#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include "file_config.h"
#include "hj_log.h"

#define LINE_BUFFER_LEN 2048
#define CONF_NAME_LEN 128

static int remove_str_tail_space(char *str)
{
    if (!str)
    {
        LOG_ERROR("str is null\n");
        return 1;
    }

    for (int i = strlen(str) - 1; i >= 0; i--)
    {
        if (str[i] != ' ' &&
            str[i] != '\t' &&
            str[i] != '\n' &&
            str[i] != '\r')
        {
            return 0;
        }
        str[i] = '\0';
    }

    return 0;
}

static int remove_str_head_space(char *str, char **new_str)
{
    if (!str)
    {
        LOG_ERROR("str is null");
        return 1;
    }

    int str_len = strlen(str);
    *new_str = str;

    for (int i = 0; i < str_len; i++)
    {
        if (str[i] != ' ' &&
            str[i] != '\t' &&
            str[i] != '\n' &&
            str[i] != '\r')
        {
            return 0;
        }

        *new_str = str + i + 1;
    }

    return 0;
}

static int is_comment(char *str_line)
{
    int str_size = strlen(str_line);

    for (int i = 0; i < str_size; i++)
    {
        if (str_line[i] == ' ' ||
            str_line[i] == '\t' ||
            str_line[i] == '\r' ||
            str_line[i] == '\n')
        {
            continue;
        }
        else if (str_line[i] == '#')
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    return 0;
}

static void free_conf_item(void *item)
{
    fconf_item_t *conf_item = (fconf_item_t *)item;

    if (!conf_item)
    {
        free(conf_item->key);
        free(conf_item->value);
        conf_item->key = NULL;
        conf_item->value = NULL;
    }
}

/* 比较相同，返回0 */
static int compare_conf_name(void *node_data, void *conf_name)
{
    fconf_item_t *item = (fconf_item_t *)node_data;
    char *key = (char *)conf_name;

    if (!strcmp(item->key, key))
    {
        return 0;
    }
    return 1;
}

static int set_conf_value(void *node_data, void *conf)
{
    fconf_item_t *item = (fconf_item_t *)conf;
    fconf_item_t *data = (fconf_item_t *)node_data;

    if (strcmp(item->key, data->key) == 0)
    {
        free_conf_item(data);
        data->key = item->key;
        data->value = item->value;
        return 0;
    }

    return 1;
}

static int get_config_item(void *node_data, void *pfile)
{
    if (!node_data || !pfile)
    {
        return 1;
    }

    FILE *fp = (FILE *)pfile;
    fconf_item_t *item = (fconf_item_t *)node_data;
    char line_buff[LINE_BUFFER_LEN] = {0};

    snprintf(line_buff, LINE_BUFFER_LEN - 1, "%s = %s\n", item->key, item->value);

    // FCONF_LOG_INFO("[%s = %s]\n", item->key, item->value);

    fputs(line_buff, fp);

    return 0;
}

static int compare_conf_item(void *item1, void *item2)
{
    if (!item1 || !item2)
    {
        LOG_ERROR("item1 or item2 is null");
        return 1;
    }

    fconf_item_t *conf_item1 = (fconf_item_t *)item1;
    fconf_item_t *conf_item2 = (fconf_item_t *)item2;

    if (!strcmp(conf_item1->key, conf_item2->key) &&
        !strcmp(conf_item1->value, conf_item2->value))
    {
        return 0;
    }

    return -1;
}

static int config_print(void *item1, void *item2)
{
    fconf_item_t *conf_item = (fconf_item_t *)item1;

    if (!conf_item)
    {
        LOG_ERROR("item1 is null");
        return 1;
    }

    printf("[%s=%s]\n", conf_item->key, conf_item->value);

    return 0;
}

int fconf_config_file_parse(fconf_t *fconf)
{
    if (!fconf)
    {
        return 3;
    }

    FILE *fp = fopen(fconf->file_name, "rb");
    if (!fp)
    {
        LOG_ERROR("open %s error", fconf->file_name);
        return 1;
    }

    char line_buf[LINE_BUFFER_LEN] = {0};
    int ret = 0;

    for (int i = 1; !feof(fp); i++)
    {
        if (!fgets(line_buf, LINE_BUFFER_LEN, fp))
        {
            LOG_ERROR("read %s error", fconf->file_name);
            fclose(fp);
            return 2;
        }

        if (is_comment(line_buf))
        {
            continue;
        }

        char old_param[LINE_BUFFER_LEN] = {0};
        char old_name[CONF_NAME_LEN] = {0};
        sscanf(line_buf, "%[^=]=%s", old_name, old_param);
        if (strlen(old_name) == 0 || strlen(old_param) == 0)
        {
            LOG_ERROR("error line");
            continue;
        }

        ret = fconf->add(fconf, old_name, old_param);
        if (ret)
        {
            LOG_ERROR("add config item error: %d", ret);
            continue;
        }
    }

    fclose(fp);
    return 0;
}

int fconf_config_file_save(fconf_t *fconf, char *file_name)
{
    if (!fconf)
    {
        return 1;
    }

    if (file_name == NULL)
    {
        file_name = fconf->file_name;
    }

    FILE *fp = fopen(file_name, "wb");
    if (!fp)
    {
        LOG_ERROR("open %s file error.", fconf->file_name);
    }
    int ret = 0;

    ret = fconf->plist->clist_traverse_list(fconf->plist, fp, get_config_item);
    if (ret)
    {
        LOG_ERROR("ret = %d", ret);
        fclose(fp);
        return 2;
    }

    fclose(fp);
    return 0;
}

int add_item_to_config(fconf_t *fconf, char *name, char *param)
{
    if (!name || !param)
    {
        LOG_ERROR("name or param is null");
        return 1;
    }

    int ret = 0;
    char *new_name = name;
    char *new_param = NULL;
    char *name_tmp = new_name;
    char *param_tmp = param;

    ret = remove_str_tail_space(name_tmp);
    if (ret)
    {
        LOG_ERROR("ret = %d", ret);
        return ret;
    }

    ret = remove_str_head_space(name_tmp, &new_name);
    if (ret)
    {
        LOG_ERROR("ret = %d", ret);
        return ret;
    }

    ret = remove_str_tail_space(param_tmp);
    if (ret)
    {
        LOG_ERROR("ret = %d", ret);
        return ret;
    }

    ret = remove_str_head_space(param_tmp, &new_param);
    if (ret)
    {
        LOG_ERROR("ret = %d", ret);
        return ret;
    }

    // FCONF_LOG_INFO("name: %s\nparam: %s\n", new_name, new_param);

    int param_size = strlen(new_param) + 1;
    int name_size = strlen(new_name) + 1;

    fconf_item_t item;

    item.key = (char *)malloc(name_size);
    item.value = (char *)malloc(param_size);

    memcpy(item.key, new_name, name_size);
    memcpy(item.value, new_param, param_size);

    ret = fconf->plist->node_add(fconf->plist, &item, sizeof(fconf_item_t));
    if (ret)
    {
        free_conf_item(&item);
        LOG_ERROR("add config item error");
    }

    return 0;
}

int fconf_remove_item(fconf_t *fconf, char *conf_name)
{
    return fconf->plist->node_del_by_data(fconf->plist, conf_name, compare_conf_name);
}

int fconf_get_config_param_by_name(fconf_t *fconf, char *conf_name, char **conf_param)
{
    fconf_item_t *item = (fconf_item_t *)fconf->plist->clist_get_data_by_data(fconf->plist, conf_name, compare_conf_name);
    if (!item)
    {
        LOG_ERROR("Can't find config item.");
        return 1;
    }

    *conf_param = item->value;
    return 0;
}

int fconf_set_config_param_by_name(fconf_t *fconf, char *conf_name, char *conf_param)
{
    int ret = 0;
    fconf_item_t *item = (fconf_item_t *)fconf->plist->clist_get_data_by_data(fconf->plist, conf_name, compare_conf_name);
    if (!item)
    {
        return fconf->add(fconf, conf_name, conf_param);
    }

    char *value = (char *)malloc(strlen(conf_param) + 1);
    if (value == NULL)
    {
        return -1;
    }
    strncpy(value, conf_param, strlen(conf_param) + 1);
    free(item->value);
    item->value = value;

    return ret;
}

int fconf_get_size(fconf_t *fconf)
{
    return fconf->plist->size;
}

int fconf_config_print(fconf_t *fconf)
{
    printf("\n");
    int ret = fconf->plist->clist_traverse_list(fconf->plist, NULL, config_print);
    printf("\n");
    return ret;
}

int fconf_init(fconf_t *p, char *file_name)
{
    clist_user_hooks_t hooks = {
        .user_data_free = free_conf_item,
        .node_compare = compare_conf_item};

    List *fconf_list = clist_new_list(&hooks);
    if (!fconf_list)
    {
        return 4;
    }

    strncpy(p->file_name, file_name, sizeof(p->file_name));
    p->plist = fconf_list;
    p->parse = fconf_config_file_parse;
    p->save = fconf_config_file_save;
    p->get = fconf_get_config_param_by_name;
    p->set = fconf_set_config_param_by_name;
    p->remove = fconf_remove_item;
    p->add = add_item_to_config;
    p->get_size = fconf_get_size;
    p->print = fconf_config_print;

    if (p->parse(p))
    {
        p->plist->clist_delete_list(p->plist);
        p->plist = NULL;
        return 5;
    }

    return 0;
}
